package com.bugstack.aop;

import com.bugstack.types.annotations.DCCValue;
import com.bugstack.types.annotations.RateLimiterAccessInterceptor;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @Author: czb
 * @Description: TODO
 * @DateTime: 2025/4/24 14:03
 **/
@Aspect
@Component
@Slf4j
public class RateLimiterAOP{

    @DCCValue("rateLimiterSwitch:close")
    private String rateLimiterSwitch;

    private final Cache<String, RateLimiter> loginRecord = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build();

    private final Cache<String, Long> blackList = CacheBuilder.newBuilder().expireAfterAccess(24, TimeUnit.HOURS).build();


    @Pointcut("@annotation(com.bugstack.types.annotations.RateLimiterAccessInterceptor)")
    public void aopPoint() {
    }

    @Around("aopPoint() && @annotation(rateLimiterAccessInterceptor)")
    public Object processed(ProceedingJoinPoint joinPoint, RateLimiterAccessInterceptor rateLimiterAccessInterceptor) throws Throwable {
        //1.从dcc配置中判断是否开启限流
        if (StringUtils.isBlank(rateLimiterSwitch) || !"open".equals(rateLimiterSwitch)){
            log.info("限流关闭 rateLimiterSwitch:{}", rateLimiterSwitch);
            return joinPoint.proceed();
        }
        //2.获取限流判断的字段
        String key = rateLimiterAccessInterceptor.key();
        String keyAttr = getAttrValue(key, joinPoint.getArgs());
        log.info("aop attr {}", keyAttr);
        //3.是否在黑名单中
        if (!"all".equals(keyAttr) && rateLimiterAccessInterceptor.blacklistCount() != 0 && null != blackList.getIfPresent(keyAttr) && blackList.getIfPresent(keyAttr) >= rateLimiterAccessInterceptor.blacklistCount()){
            log.info("用户被限流黑名单拦截 keyAttr:{}", keyAttr);
            return fallbackMethodResult(joinPoint, rateLimiterAccessInterceptor.fallbackMethod());
        }
        //4.判断是否限流
        RateLimiter rateLimiter = loginRecord.getIfPresent(keyAttr);
        if (null == rateLimiter) {
            rateLimiter = RateLimiter.create(rateLimiterAccessInterceptor.permitsPerSecond());
            loginRecord.put(keyAttr, rateLimiter);
        }
        if (!rateLimiter.tryAcquire()) {
            if (null == blackList.getIfPresent(keyAttr)) {
                blackList.put(keyAttr, 1L);
            }else {
                blackList.put(keyAttr, blackList.getIfPresent(keyAttr) + 1);
            }
            log.info("用户被限流拦截 keyAttr:{}", keyAttr);
            return fallbackMethodResult(joinPoint, rateLimiterAccessInterceptor.fallbackMethod());
        }
        return joinPoint.proceed();
    }

    private Object fallbackMethodResult(ProceedingJoinPoint jp, String fallbackMethod) throws Throwable {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
        return method.invoke(jp.getThis(), jp.getArgs());
    }

    /**
     * 实际根据自身业务调整，主要是为了获取通过某个值做拦截
     */
    public String getAttrValue(String attr, Object[] args) {
        if (args[0] instanceof String) {
            return args[0].toString();
        }
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (StringUtils.isNotBlank(filedValue)) {
                    break;
                }
                // filedValue = BeanUtils.getProperty(arg, attr);
                // fix: 使用lombok时，uId这种字段的get方法与idea生成的get方法不同，会导致获取不到属性值，改成反射获取解决
                filedValue = String.valueOf(this.getValueByName(arg, attr));
            } catch (Exception e) {
                log.error("获取路由属性值失败 attr：{}", attr, e);
            }
        }
        return filedValue;
    }

    /**
     * 获取对象的特定属性值
     *
     * @param item 对象
     * @param name 属性名
     * @return 属性值
     * @author tang
     */
    private Object getValueByName(Object item, String name) {
        try {
            Field field = getFieldByName(item, name);
            if (field == null) {
                return null;
            }
            field.setAccessible(true);
            Object o = field.get(item);
            field.setAccessible(false);
            return o;
        } catch (IllegalAccessException e) {
            return null;
        }
    }

    /**
     * 根据名称获取方法，该方法同时兼顾继承类获取父类的属性
     *
     * @param item 对象
     * @param name 属性名
     * @return 该属性对应方法
     * @author tang
     */
    private Field getFieldByName(Object item, String name) {
        try {
            Field field;
            try {
                field = item.getClass().getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                field = item.getClass().getSuperclass().getDeclaredField(name);
            }
            return field;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

}
